#include "abc-article-manager-controller.h"

#include <functional>

#include "util/net/http-request.h"
#include "util/net/http-response.h"
#include "util/html/template/factory.h"
#include "util/logger/logger.h"
#include "util/strutil.h"
#include "util/fs-util.h"

#include "book/book.h"

#include "abc-server.h"
#include "abc-article-manager-synchronizer.h"


static const char *title() {
    return "AbcArticleManagerController ";
}

AbcArticleManagerController::AbcArticleManagerController(AbcServer &server)
    : server_(server), manager_(new AbcArticleManagerSynchronizer(*this, server.executor())) {

    LOGD << title() << "constructed";
}

AbcArticleManagerController::~AbcArticleManagerController() {
    LOGD << title() << "destructed";
    delete manager_;
    manager_ = nullptr;
}

void AbcArticleManagerController::handleHttpRequest(const HttpRequest &request, HttpResponse &response) {
    using strutil::startWith;

    if (request.under(prefix::article::Article)) {
        handleArticleRequest(request, response);
        return;
    }

    if (request.under(prefix::article::ArticleControll)) {
        handleControllRequest(request, response);
        return;
    }
}

void AbcArticleManagerController::notifyDataGenerated(std::shared_ptr<book::Book> book) {
    LOGD << title() << "notify data generated: [" << book->generatedDir() << "]";
    book_ = book;
    
    manager_->afterBookDataDirActivated(book_->generatedDir());
}

static fs::path selectContentFilePath(const fs::path &dataDir, const std::string &relativeUri) {
    if (relativeUri.empty()) {
        return dataDir / "a.bookmenu.html";
    }

    if (fs::is_directory(dataDir / relativeUri)) {
        return dataDir / relativeUri / "a.chaptermenu.html";
    }

    return dataDir / (relativeUri + ".content.html");
}

static std::string prevUri(const book::IndexTree::Node &node) {
    const book::IndexTree::Node *p = node.prevArticle();
    if (p) {
        std::string result;
        result += prefix::article::Article;
        result += '/';
        result += p->uriPath();
        return result;
    } else {
        return std::string();
    }
}

static std::string nextUri(const book::IndexTree::Node &node) {
    const book::IndexTree::Node *p = node.nextArticle();
    if (p) {
        std::string result;
        result += prefix::article::Article;
        result += '/';
        result += p->uriPath();
        return result;
    } else {
        return std::string();
    }
}

static std::string prevTitle(const book::IndexTree::Node &node) {
    const book::IndexTree::Node *p = node.prevArticle();
    if (p) {
        return p->data().title();
    } else {
        return std::string();
    }
}

static std::string nextTitle(const book::IndexTree::Node &node) {
    const book::IndexTree::Node *p = node.nextArticle();
    if (p) {
        return p->data().title();
    } else {
        return std::string();
    }
}

void AbcArticleManagerController::handleArticleRequest(const HttpRequest &request, HttpResponse &response) {
    if (!book_) {
        LOGE << title() << "book not generated";
        response.setStatusCode(HttpStatusCode::NotFound);
        return;
    }

    const fs::path &dataDir = book_->generatedDir();

    const std::string relativeUri = request.relative(prefix::article::Article);

    const std::string_view ext = request.ext();
    if (ext == "png" || ext == "svg") {
        const fs::path imgPath = book_->imgFilePath(relativeUri);
        if (!fs::exists(imgPath)) {
            LOGE << title() << "image fs path [" << imgPath << "] not exist";
            response.setStatusCode(HttpStatusCode::NotFound);
            return;
        }
        response.setContentType(http::ContentType::fromExt(ext));
        const fs::file_time_type writeTime = fs::last_write_time(imgPath);
        const std::time_t seconds = std::chrono::duration_cast<std::chrono::seconds>(writeTime.time_since_epoch()).count();
        const std::string curEtag = strutil::tostr(seconds);
        response.setEtag(curEtag);
        if (request.ifNonMatch() == curEtag) {
            response.setStatusCode(HttpStatusCode::NotModified);
            return;
        }
        response.buffer() = fsutil::content(imgPath);
        return;
    }
    
    const html::tmplt::Template *t = server_.httpTemplateFactory().byPath("book.html");
    if (!t) {
        LOGE << title() << "cannot find template 'book.html'";
        response.setStatusCode(HttpStatusCode::NotFound);
        return;
    }

    const fs::path contentFilePath = selectContentFilePath(dataDir, relativeUri);
    const fs::path articleMenuPath = dataDir / (relativeUri + ".menu.html");
    const fs::path bookMenuPath = dataDir / "a.bookmenu.html";

    std::map<std::string, DynamicObj> objs;
    
    objs["content"] = fsutil::content(contentFilePath);
    objs["article-menu"] = fsutil::content(articleMenuPath);
    objs["book-menu"] = fsutil::content(bookMenuPath);

    const book::IndexTree::Node *node = book_->indexTree().findNodeByUri(relativeUri);
    if (node) {
        objs["title"] = node->data().title();
        objs["prev-uri"] = prevUri(*node);
        objs["prev-title"] = prevTitle(*node);
        objs["next-uri"] = nextUri(*node);
        objs["next-title"] = nextTitle(*node);
    } else {
        objs["title"] = "";
        objs["prev-uri"] = "";
        objs["prev-title"] = "";
        objs["next-uri"] = "";
        objs["next-title"] = "";
    }

    std::ostringstream ss;
    t->render(objs, ss);

    response.buffer() = ss.str();
}

void AbcArticleManagerController::handleControllRequest(const HttpRequest &request, HttpResponse &response) {
    manager_->notifyRepoPushed();
}
